
/*
  此项目模仿自稚晖君暂未开源的 MonoUI，用于实现类似 UltraLink 的丝滑界面

  有四个版本：

    * 分辨率 128 * 128 ：主菜单，列表和关于本机页基本还原了 UltraLink 的界面和动态效果，电压测量页为原创设计
    * 分辨率 128 * 64 ：主菜单模仿 UltraLink 重新设计，去掉了小标题，电压测量页重新设计，关于本机页面改为列表，列表适配了该分辨率
    * 分辨率 128 * 32 ：在 128 * 64 分辨率的基础上，主界面只保留图标，电压测量页重新设计
    * 通用版本：仅保留列表，主菜单也改为列表，删除电压测量页和与列表无关的动画（保留弹窗效果），经过简单修改可以适配任何分辨率，任何行高度的情况

  WouoUI v2.0 功能：

    * 全部使用非线性的平滑缓动动画，包括列表，弹窗，甚至进度条
    * 优化平滑动画算法到只有两行，分类别定义平滑权重，并且每个权重值都分别可调
    * 可以打断的非线性动画，当前动画未结束但下一次动画已经被触发时，动画可以自然过渡
    * 非通用版本分别适配了类似 UltraLink 主菜单的磁贴界面（因为让我想起 WP7 的 Metron 风格，所以称之为磁贴）
    * 通用版本仅保留列表类界面，经过简单修改可以适配所有分辨率的屏幕，包括屏幕内行数不是整数的情况
    * 列表菜单，列表可以无限延长
    * 列表文字选择框，选择框可根据选择的字符串长度自动伸缩，进入菜单时从列表开头从长度 0 展开，转到上一级列表时，长度和纵坐标平滑移动到上一级选择的位置
    * 列表单选框，储存数据时也储存该值在列表中所在的位置，展开列表时根据每行开头的字符判断是否绘制外框，再根据位置数据判断是否绘制里面的点
    * 列表多选框，储存数据的数组跟多选框列表的行数对应，不要求连续排列，展开列表时根据每行开头的字符判断是否绘制外框，再根据行数对应的储存数据位置的数值是否为1判断是否绘制里面的点
    * 列表显示数值，与多选框原理相似，但不涉及修改操作
    * 列表展开动画，初始化列表时，可以选择列表从头开始展开，或者从上次选中的位置展开
    * 图标展开动画，初始化磁贴类界面时，可以选择图标从头开始展开，或者从上次选中的位置展开
    * 弹出窗口，实现了窗口弹出的动画效果，可以自定义最大值，最小值，步进值，需要修改的参数等，窗口独立运行，调用非常简单
    * 弹出窗口背景虚化可选项，背景虚化会产生卡顿感，但删掉代码有些可惜，因此做成可选项，默认关闭
    * 亮度调节，在弹出窗口中调节亮度值可以实时改变当前亮度值
    * 旋钮功能，使用EC11旋钮控制，旋钮方向可以软件调整，内置一个USB控制电脑的示例，在睡眠模式下旋转调整音量或者亮度，短按输入一个键值，长按进入主菜单，旋钮消抖时长等参数可以在弹出窗口中调整
    * 循环模式，选择项超过头尾时，选择项跳转到另一侧继续，列表和磁贴类可以分别选择
    * 黑暗模式，其实本来就是黑暗模式，是增加了白天模式，默认开启黑暗模式
    * 消失动画适配两种模式，一种是渐变成全黑，另一种渐变成全白
    * 断电存储，用简单直观的方式将每种功能参数写入EEPROM，只在修改过参数，进入睡眠模式时写入，避免重复擦写，初始化时检查11个标志位，允许一位误码

  项目参考：

    * B站：路徍要什么自行车：在线仿真：https://wokwi.com/projects/350306511434547796，https://www.bilibili.com/video/BV1HA411S7pv/
    * Github：createskyblue：OpenT12：https://github.com/createskyblue/OpenT12
    * Github：peng-zhihui：OpenHeat：https://github.com/peng-zhihui/OpenHeat

  注意事项：

    * 示例使用的单片机为 STM32F103C8T6 ，旋钮为 EC11 ，旋钮旋转使用中断程序，旋钮质量不好可能使界面卡死，原因不明

*/

#include "menu.h"
extern u8g2_t u8g2;

// 总目录，缩进表示页面层级
enum
{
    M_WINDOW,
    M_SLEEP,
    M_MAIN,
    M_EDITOR,
    M_KNOB,
    M_KRF,
    M_KPF,
    M_VOLT,
    M_SETTING,
    M_ABOUT,
};

// 状态，初始化标签
enum
{
    S_FADE,      // 转场动画
    S_WINDOW,    // 弹窗初始化
    S_LAYER_IN,  // 层级初始化
    S_LAYER_OUT, // 层级初始化
    S_NONE,      // 直接选择页面
};

// 菜单结构体
typedef struct MENU
{
    char *m_select;
} M_SELECT;
/************************************* 定义内容 *************************************/

/************************************* 文字内容 *************************************/

M_SELECT main_menu[] =
    {
        {"Sleep"},
        {"Editor"},
        {"Volt"},
        {"Setting"},
};

M_SELECT editor_menu[] =
    {
        {"[ Editor ]"},
        {"- Function 0"},
        {"- Function 1"},
        {"- Function 2"},
        {"- Function 3"},
        {"- Function 4"},
        {"- Function 5"},
        {"- Function 6"},
        {"- Function 7"},
        {"- Function 8"},
        {"- Function 9"},
        {"- Knob"},
};

M_SELECT knob_menu[] =
    {
        {"[ Knob ]"},
        {"# Rotate Func"},
        {"$ Press Func"},
};

M_SELECT krf_menu[] =
    {
        {"[ Rotate Function ]"},
        {"--------------------------"},
        {"= Disable"},
        {"--------------------------"},
        {"= Volume"},
        {"= Brightness"},
        {"--------------------------"},
};

M_SELECT kpf_menu[] =
    {
        {"[ Press Function ]"},
        {"--------------------------"},
        {"= Disable"},
        {"--------------------------"},
        {"= A"},
        {"= B"},
        {"= C"},
        {"= D"},
        {"= E"},
        {"= F"},
        {"= G"},
        {"= H"},
        {"= I"},
        {"= J"},
        {"= K"},
        {"= L"},
        {"= M"},
        {"= N"},
        {"= O"},
        {"= P"},
        {"= Q"},
        {"= R"},
        {"= S"},
        {"= T"},
        {"= U"},
        {"= V"},
        {"= W"},
        {"= X"},
        {"= Y"},
        {"= Z"},
        {"--------------------------"},
        {"= 0"},
        {"= 1"},
        {"= 2"},
        {"= 3"},
        {"= 4"},
        {"= 5"},
        {"= 6"},
        {"= 7"},
        {"= 8"},
        {"= 9"},
        {"--------------------------"},
        {"= Esc"},
        {"= F1"},
        {"= F2"},
        {"= F3"},
        {"= F4"},
        {"= F5"},
        {"= F6"},
        {"= F7"},
        {"= F8"},
        {"= F9"},
        {"= F10"},
        {"= F11"},
        {"= F12"},
        {"--------------------------"},
        {"= Left Ctrl"},
        {"= Left Shift"},
        {"= Left Alt"},
        {"= Left Win"},
        {"= Right Ctrl"},
        {"= Right Shift"},
        {"= Right Alt"},
        {"= Right Win"},
        {"--------------------------"},
        {"= Caps Lock"},
        {"= Backspace"},
        {"= Return"},
        {"= Insert"},
        {"= Delete"},
        {"= Tab"},
        {"--------------------------"},
        {"= Home"},
        {"= End"},
        {"= Page Up"},
        {"= Page Down"},
        {"--------------------------"},
        {"= Up Arrow"},
        {"= Down Arrow"},
        {"= Left Arrow"},
        {"= Right Arrow"},
        {"--------------------------"},
};

M_SELECT volt_menu[] =
    {
        {"A0"},
        {"A1"},
        {"A2"},
        {"A3"},
        {"A4"},
        {"A5"},
        {"A6"},
        {"A7"},
        {"B0"},
        {"B1"},
};

M_SELECT setting_menu[] =
    {
        {"[ Setting ]"},
        {"~ Disp Bri"},
        {"~ Tile Ani"},
        {"~ List Ani"},
        {"~ Win Ani"},
        {"~ Spot Ani"},
        {"~ Tag Ani"},
        {"~ Fade Ani"},
        {"~ Btn SPT"},
        {"~ Btn LPT"},
        {"+ T Ufd Fm Scr"},
        {"+ L Ufd Fm Scr"},
        {"+ T Loop Mode"},
        {"+ L Loop Mode"},
        {"+ Win Bokeh Bg"},
        {"+ Knob Rot Dir"},
        {"+ Dark Mode"},
        {"- [ About ]"},
};

M_SELECT about_menu[] =
    {
        {"[ WouoUI ]"},
        {"- Version: v2.0"},
        {"- Board: STM32F103"},
        {"- Ram: 20k"},
        {"- Flash: 64k"},
        {"- Freq: 72Mhz"},
        {"- Creator: RQNG"},
        {"- Billi UID: 9182439"},
};

/************************************* 图片内容 *************************************/

const uint8_t main_icon_pic[][120] U8X8_PROGMEM =
    {
        {0xFF, 0xFF, 0xFF, 0x3F, 0xFF, 0xFF, 0xFF, 0x3F, 0xFF, 0xFF, 0xFF, 0x3F, 0xFF, 0xFF, 0xF1, 0x3F,
         0xFF, 0xFF, 0xC3, 0x3F, 0xFF, 0xFF, 0x87, 0x3F, 0xFF, 0xFF, 0x07, 0x3F, 0xFF, 0xFF, 0x0F, 0x3E,
         0xFF, 0xFF, 0x0F, 0x3E, 0xFF, 0xFF, 0x0F, 0x3C, 0xFF, 0xFF, 0x0F, 0x3C, 0xFF, 0xFF, 0x0F, 0x38,
         0xFF, 0xFF, 0x0F, 0x38, 0xFF, 0xFF, 0x0F, 0x38, 0xFF, 0xFF, 0x07, 0x38, 0xFF, 0xFF, 0x07, 0x38,
         0xFF, 0xFF, 0x03, 0x38, 0xF7, 0xFF, 0x01, 0x38, 0xE7, 0xFF, 0x00, 0x3C, 0x87, 0x3F, 0x00, 0x3C,
         0x0F, 0x00, 0x00, 0x3E, 0x0F, 0x00, 0x00, 0x3E, 0x1F, 0x00, 0x00, 0x3F, 0x3F, 0x00, 0x80, 0x3F,
         0x7F, 0x00, 0xC0, 0x3F, 0xFF, 0x01, 0xF0, 0x3F, 0xFF, 0x07, 0xFC, 0x3F, 0xFF, 0xFF, 0xFF, 0x3F,
         0xFF, 0xFF, 0xFF, 0x3F, 0xFF, 0xFF, 0xFF, 0x3F},
        {0xFF, 0xFF, 0xFF, 0x3F, 0xFF, 0xFF, 0xFF, 0x3F, 0xFF, 0xFF, 0xFF, 0x3F, 0xFF, 0xF9, 0xE7, 0x3F,
         0xFF, 0xF9, 0xE7, 0x3F, 0xFF, 0xF9, 0xE7, 0x3F, 0xFF, 0xF0, 0xE7, 0x3F, 0x7F, 0xE0, 0xE7, 0x3F,
         0x7F, 0xE0, 0xC3, 0x3F, 0x7F, 0xE0, 0xC3, 0x3F, 0x7F, 0xE0, 0xC3, 0x3F, 0x7F, 0xE0, 0xE7, 0x3F,
         0xFF, 0xF0, 0xE7, 0x3F, 0xFF, 0xF9, 0xE7, 0x3F, 0xFF, 0xF9, 0xE7, 0x3F, 0xFF, 0xF9, 0xE7, 0x3F,
         0xFF, 0xF9, 0xE7, 0x3F, 0xFF, 0xF9, 0xC3, 0x3F, 0xFF, 0xF9, 0x81, 0x3F, 0xFF, 0xF0, 0x81, 0x3F,
         0xFF, 0xF0, 0x81, 0x3F, 0xFF, 0xF0, 0x81, 0x3F, 0xFF, 0xF9, 0x81, 0x3F, 0xFF, 0xF9, 0xC3, 0x3F,
         0xFF, 0xF9, 0xE7, 0x3F, 0xFF, 0xF9, 0xE7, 0x3F, 0xFF, 0xF9, 0xE7, 0x3F, 0xFF, 0xFF, 0xFF, 0x3F,
         0xFF, 0xFF, 0xFF, 0x3F, 0xFF, 0xFF, 0xFF, 0x3F},
        {0xFF, 0xFF, 0xFF, 0x3F, 0xFF, 0xFF, 0xFF, 0x3F, 0xEF, 0xFF, 0xFF, 0x3F, 0xC7, 0xFF, 0xFF, 0x3F,
         0xC7, 0xF3, 0xFF, 0x3F, 0x83, 0xC0, 0xFF, 0x3F, 0xEF, 0xCC, 0xFF, 0x3F, 0x6F, 0x9E, 0xFF, 0x3F,
         0x6F, 0x9E, 0xFF, 0x3F, 0x2F, 0x3F, 0xFF, 0x3F, 0x2F, 0x3F, 0xFF, 0x3F, 0x8F, 0x7F, 0xFE, 0x3F,
         0x8F, 0x7F, 0xFE, 0x39, 0x8F, 0x7F, 0xFE, 0x39, 0xCF, 0xFF, 0xFC, 0x3C, 0xCF, 0xFF, 0xFC, 0x3C,
         0xEF, 0xFF, 0xFC, 0x3C, 0xEF, 0xFF, 0x79, 0x3E, 0xEF, 0xFF, 0x79, 0x3E, 0xEF, 0xFF, 0x33, 0x3F,
         0xEF, 0xFF, 0x33, 0x3F, 0xEF, 0xFF, 0x87, 0x3F, 0xEF, 0xFF, 0xCF, 0x3F, 0xEF, 0xFF, 0x7F, 0x3E,
         0xEF, 0xFF, 0x7F, 0x38, 0x0F, 0x00, 0x00, 0x30, 0xFF, 0xFF, 0x7F, 0x38, 0xFF, 0xFF, 0x7F, 0x3E,
         0xFF, 0xFF, 0xFF, 0x3F, 0xFF, 0xFF, 0xFF, 0x3F},
        {0xFF, 0xFF, 0xFF, 0x3F, 0xFF, 0xFF, 0xFF, 0x3F, 0xFF, 0xFF, 0xFF, 0x3F, 0xFF, 0xFF, 0xFF, 0x3F,
         0xFF, 0x1F, 0xFE, 0x3F, 0xFF, 0x1F, 0xFE, 0x3F, 0xFF, 0x0C, 0xCC, 0x3F, 0x7F, 0x00, 0x80, 0x3F,
         0x3F, 0x00, 0x00, 0x3F, 0x3F, 0xE0, 0x01, 0x3F, 0x7F, 0xF8, 0x87, 0x3F, 0x7F, 0xFC, 0x8F, 0x3F,
         0x3F, 0xFC, 0x0F, 0x3F, 0x0F, 0x3E, 0x1F, 0x3C, 0x0F, 0x1E, 0x1E, 0x3C, 0x0F, 0x1E, 0x1E, 0x3C,
         0x0F, 0x3E, 0x1F, 0x3C, 0x3F, 0xFC, 0x0F, 0x3F, 0x7F, 0xFC, 0x8F, 0x3F, 0x7F, 0xF8, 0x87, 0x3F,
         0x3F, 0xE0, 0x01, 0x3F, 0x3F, 0x00, 0x00, 0x3F, 0x7F, 0x00, 0x80, 0x3F, 0xFF, 0x0C, 0xCC, 0x3F,
         0xFF, 0x1F, 0xFE, 0x3F, 0xFF, 0x1F, 0xFE, 0x3F, 0xFF, 0xFF, 0xFF, 0x3F, 0xFF, 0xFF, 0xFF, 0x3F,
         0xFF, 0xFF, 0xFF, 0x3F, 0xFF, 0xFF, 0xFF, 0x3F},
};

/************************************* 页面变量 *************************************/

// OLED变量
#define DISP_H 64  // 屏幕高度
#define DISP_W 128 // 屏幕宽度
uint8_t *buf_ptr;  // 指向屏幕缓冲的指针
uint16_t buf_len;  // 缓冲长度

// UI变量
#define UI_DEPTH 20  // 最深层级数
#define UI_MNUMB 100 // 菜单数量
#define UI_PARAM 16  // 参数数量
enum
{
    DISP_BRI,  // 屏幕亮度
    TILE_ANI,  // 磁贴动画速度
    LIST_ANI,  // 列表动画速度
    WIN_ANI,   // 弹窗动画速度
    SPOT_ANI,  // 聚光动画速度
    TAG_ANI,   // 标签动画速度
    FADE_ANI,  // 消失动画速度
    BTN_SPT,   // 按键短按时长
    BTN_LPT,   // 按键长按时长
    TILE_UFD,  // 磁贴图标从头展开开关
    LIST_UFD,  // 菜单列表从头展开开关
    TILE_LOOP, // 磁贴图标循环模式开关
    LIST_LOOP, // 菜单列表循环模式开关
    WIN_BOK,   // 弹窗背景虚化开关
    KNOB_DIR,  // 旋钮方向切换开关
    DARK_MODE, // 黑暗模式开关
};
struct
{
    bool init;
    uint8_t num[UI_MNUMB];
    uint8_t select[UI_DEPTH];
    uint8_t layer;
    uint8_t index;
    uint8_t state;
    bool sleep;
    uint8_t fade;
    uint8_t param[UI_PARAM];
} ui;

// 磁贴变量
// 所有磁贴页面都使用同一套参数
#define TILE_B_FONT u8g2_font_helvB18_tr // 磁贴大标题字体
#define TILE_B_TITLE_H 18                // 磁贴大标题字体高度
#define TILE_ICON_H 30                   // 磁贴图标高度
#define TILE_ICON_W 30                   // 磁贴图标宽度
#define TILE_ICON_S 36                   // 磁贴图标间距
#define TILE_INDI_H 27                   // 磁贴大标题指示器高度
#define TILE_INDI_W 7                    // 磁贴大标题指示器宽度
#define TILE_INDI_S 36                   // 磁贴大标题指示器上边距
struct
{
    float title_y_calc;
    float title_y_trg_calc;
    int16_t temp;
    bool select_flag;
    float icon_x;
    float icon_x_trg;
    float icon_y;
    float icon_y_trg;
    float indi_x;
    float indi_x_trg;
    float title_y;
    float title_y_trg;
} tile;

// 列表变量
// 默认参数

#define LIST_FONT u8g2_font_HelvetiPixel_tr // 列表字体
#define LIST_TEXT_H 8                       // 列表每行文字字体的高度
#define LIST_LINE_H 16                      // 列表单行高度
#define LIST_TEXT_S 4                       // 列表每行文字的上边距，左边距和右边距，下边距由它和字体高度和行高度决定
#define LIST_BAR_W 5                        // 列表进度条宽度，需要是奇数，因为正中间有1像素宽度的线
#define LIST_BOX_R 0.5                      // 列表选择框圆角
// #define LIST_BOX_R 1 // 列表选择框圆角

/*
//超窄行高度测试
#define   LIST_FONT           u8g2_font_4x6_tr            //列表字体
#define   LIST_TEXT_H         5                           //列表每行文字字体的高度
#define   LIST_LINE_H         7                           //列表单行高度
#define   LIST_TEXT_S         1                           //列表每行文字的上边距，左边距和右边距，下边距由它和字体高度和行高度决定
#define   LIST_BAR_W          7                           //列表进度条宽度，需要是奇数，因为正中间有1像素宽度的线
#define   LIST_BOX_R          0.5                         //列表选择框圆角
*/
struct
{
    uint8_t line_n;
    int16_t temp;
    bool loop;
    float y;
    float y_trg;
    float box_x;
    float box_x_trg;
    float box_y;
    float box_y_trg[UI_DEPTH];
    float bar_y;
    float bar_y_trg;
} list;

// 电压测量页面变量
// 开发板模拟引脚
// uint8_t   analog_pin[10] = { PA0, PA1, PA2, PA3, PA4, PA5, PA6, PA7, PB0, PB1 };
uint8_t analog_pin[10];
// 曲线相关
#define WAVE_SAMPLE 20  // 采集倍数
#define WAVE_W 94       // 波形宽度
#define WAVE_L 24       // 波形左边距
#define WAVE_U 0        // 波形上边距
#define WAVE_MAX 27     // 最大值
#define WAVE_MIN 4      // 最小值
#define WAVE_BOX_H 32   // 波形边框高度
#define WAVE_BOX_W 94   // 波形边框宽度
#define WAVE_BOX_L_S 24 // 波形边框左边距
// 列表和文字背景框相关
#define VOLT_FONT u8g2_font_helvB18_tr // 电压数字字体
#define VOLT_TEXT_BG_L_S 24            // 文字背景框左边距
#define VOLT_TEXT_BG_W 94              // 文字背景框宽度
#define VOLT_TEXT_BG_H 29              // 文字背景框高度
struct
{
    int ch0_adc[WAVE_SAMPLE * WAVE_W];
    int ch0_wave[WAVE_W];
    int val;
    float text_bg_r;
    float text_bg_r_trg;
} volt;

// 选择框变量

// 默认参数
#define CHECK_BOX_L_S 95 // 选择框在每行的左边距
#define CHECK_BOX_U_S 2  // 选择框在每行的上边距
#define CHECK_BOX_F_W 12 // 选择框外框宽度
#define CHECK_BOX_F_H 12 // 选择框外框高度
#define CHECK_BOX_D_S 2  // 选择框里面的点距离外框的边距

/*
//超窄行高度测试
#define   CHECK_BOX_L_S       99                          //选择框在每行的左边距
#define   CHECK_BOX_U_S       0                           //选择框在每行的上边距
#define   CHECK_BOX_F_W       5                           //选择框外框宽度
#define   CHECK_BOX_F_H       5                           //选择框外框高度
#define   CHECK_BOX_D_S       1                           //选择框里面的点距离外框的边距
*/
struct
{
    uint8_t *v;
    uint8_t *m;
    uint8_t *s;
    uint8_t *s_p;
} check_box;

// 弹窗变量
#define WIN_FONT u8g2_font_HelvetiPixel_tr // 弹窗字体
#define WIN_H 32                           // 弹窗高度
#define WIN_W 102                          // 弹窗宽度
#define WIN_BAR_W 92                       // 弹窗进度条宽度
#define WIN_BAR_H 7                        // 弹窗进度条高度
#define WIN_Y -WIN_H - 2                   // 弹窗竖直方向出场起始位置
#define WIN_Y_TRG -WIN_H - 2               // 弹窗竖直方向退场终止位置
struct
{
    // uint8_t
    uint8_t *value;
    uint8_t max;
    uint8_t min;
    uint8_t step;

    struct MENU *bg;
    uint8_t index;
    char title[20];
    uint8_t select;
    uint8_t l;
    uint8_t u;
    float bar;
    float bar_trg;
    float y;
    float y_trg;
} win;

// 聚光灯变量
struct
{
    float l;
    float l_trg;
    float r;
    float r_trg;
    float u;
    float u_trg;
    float d;
    float d_trg;
} spot;
/************************************* USB 相关 *************************************/

// #include <USBComposite.h>

// USBHID HID;

// const uint8_t reportDescription[] =
//{
//    HID_CONSUMER_REPORT_DESCRIPTOR(),
//    HID_KEYBOARD_REPORT_DESCRIPTOR()
// };

// HIDConsumer Consumer(HID);
// HIDKeyboard Keyboard(HID);

// void hid_init()
//{
//   HID.begin(reportDescription, sizeof(reportDescription));
//   while (!USBComposite);
// }

/********************************** 自定义功能变量 **********************************/

// 旋钮功能变量
#define KNOB_PARAM 4
#define KNOB_DISABLE 0
#define KNOB_ROT_VOL 1
#define KNOB_ROT_BRI 2
enum
{
    KNOB_ROT,   // 睡眠下旋转旋钮的功能，0 禁用，1 音量，2 亮度
    KNOB_COD,   // 睡眠下短按旋钮输入的字符码，0 禁用
    KNOB_ROT_P, // 旋转旋钮功能在单选框中选择的位置
    KNOB_COD_P, // 字符码在单选框中选择的位置
};
// struct
//{
//   uint8_t param[KNOB_PARAM]; //禁用在列表的第2个选项，第0个是标题，第1个是分界线
// } knob;
uint8_t knob_param[KNOB_PARAM] = {KNOB_DISABLE, KNOB_DISABLE, 2, 2};

/************************************* 断电保存 *************************************/

// #include <EEPROM.h>

////EEPROM变量
// #define   EEPROM_CHECK        11
// struct
//{
//   bool    init;
//   bool    change;
//   int     address;
//   uint8_t check;
//   uint8_t check_param[EEPROM_CHECK] = { 'a', 'b', 'c', 'd', 'e', 'f','g', 'h', 'i', 'j', 'k' };
// } eeprom;

////EEPROM写数据，回到睡眠时执行一遍
// void eeprom_write_all_data()
//{
//   eeprom.address = 0;
//   for (uint8_t i = 0; i < EEPROM_CHECK; ++i)    EEPROM.write(eeprom.address + i, eeprom.check_param[i]);  eeprom.address += EEPROM_CHECK;
//   for (uint8_t i = 0; i < UI_PARAM; ++i)        EEPROM.write(eeprom.address + i, ui.param[i]);            eeprom.address += UI_PARAM;
//   for (uint8_t i = 0; i < KNOB_PARAM; ++i)      EEPROM.write(eeprom.address + i, knob.param[i]);          eeprom.address += KNOB_PARAM;
// }

////EEPROM读数据，开机初始化时执行一遍
// void eeprom_read_all_data()
//{
//   eeprom.address = EEPROM_CHECK;
//   for (uint8_t i = 0; i < UI_PARAM; ++i)        ui.param[i]   = EEPROM.read(eeprom.address + i);          eeprom.address += UI_PARAM;
//   for (uint8_t i = 0; i < KNOB_PARAM; ++i)      knob.param[i] = EEPROM.read(eeprom.address + i);          eeprom.address += KNOB_PARAM;
// }

////开机检查是否已经修改过，没修改过则跳过读配置步骤，用默认设置
// void eeprom_init()
//{
//   eeprom.check = 0;
//   eeprom.address = 0; for (uint8_t i = 0; i < EEPROM_CHECK; ++i)  if (EEPROM.read(eeprom.address + i) != eeprom.check_param[i])  eeprom.check ++;
//   if (eeprom.check <= 1) eeprom_read_all_data();  //允许一位误码
//   else ui_param_init();
// }

/************************************* 旋钮相关 *************************************/

// 可按下旋钮引脚
#define AIO PB12
#define BIO PB13
#define SW PB14

// 按键ID
#define BTN_ID_CC 0 // 逆时针旋转、上、右
#define BTN_ID_CW 1 // 顺时针旋转、下、左
#define BTN_ID_SP 2 // 确认短按、确认、中
#define BTN_ID_LC 3 // 上、右长按
#define BTN_ID_LW 4 // 下、左长按
#define BTN_ID_LP 5 // 确认长按

// 按键变量
#define BTN_PARAM_TIMES 2 // 由于uint8_t最大值可能不够，但它存储起来方便，这里放大两倍使用
struct
{
    uint8_t id;
    bool flag;
    bool pressed;
    bool CW_1;
    bool CW_2;
    //    bool val;
    //    bool val_last;
    bool alv;
    bool blv;
    uint16_t count;
} btn;
typedef struct
{
    bool val;
    bool val_last;
} KEY;
KEY key[3] = {false};
#define LOW 0
bool get_key_val(uint8_t ch)
{
    switch (ch)
    {
    case 0:
        return KEY_UP(); // 上、右
        break;
    case 1:
        return KEY_DOWN(); // 下、左
        break;
    case 2:
        return KEY_CENTER(); // 确认、中
        break;
    default:
        break;
    }
}
void knob_inter()
{
    btn.alv = EC11_CLK();
    btn.blv = EC11_DT();
    if (!btn.flag && btn.alv == LOW)
    {
        btn.CW_1 = btn.blv;
        btn.flag = true;
    }
    if (btn.flag && btn.alv)
    {
        btn.CW_2 = !btn.blv;
        if (btn.CW_1 && btn.CW_2)
        {
            btn.id = !ui.param[KNOB_DIR];
            btn.pressed = true;
        }
        if (btn.CW_1 == false && btn.CW_2 == false)
        {
            btn.id = ui.param[KNOB_DIR]; // ui.param[KNOB_DIR] = 0;   // 旋钮方向切换开关
            btn.pressed = true;
        }
        btn.flag = false;
    }
}

void btn_scan()
{
    knob_inter();
    for (uint8_t i = 0; i < (sizeof(key) / sizeof(KEY)); ++i)
    {
        key[i].val = get_key_val(i);
        if (key[i].val_last != key[i].val) // 发生改变
        {
            key[i].val_last = key[i].val; // 更新状态
            HAL_Delay(ui.param[BTN_SPT] * BTN_PARAM_TIMES);
            key[i].val = get_key_val(i);
            if (key[i].val == LOW)
            {
                btn.pressed = true;
                btn.count = 0;
                while (!get_key_val(i))
                {
                    btn.count++;
                    HAL_Delay(1);
                }
                if (btn.count < ui.param[BTN_LPT] * BTN_PARAM_TIMES) // 短按
                                                                     //                    btn.id = BTN_ID_SP;
                    btn.id = i;
                else // 长按
                    btn.id = i + 3;
                //                    btn.id = BTN_ID_LP;
            }
        }
    }
}

/********************************* print函数 ********************************/
void Printint(u8g2_t *u8g2, u8g2_uint_t x, u8g2_uint_t y, uint8_t var)
{
    char var_buff[100];                 // 用来存ASCII码
    sprintf(var_buff, "%3d", var);      // 传递变量（十进制）
    u8g2_DrawStr(u8g2, x, y, var_buff); // 显示
}
void Printfloat(u8g2_t *u8g2, u8g2_uint_t x, u8g2_uint_t y, float var)
{
    char var_buff[100];                 // 用来存ASCII码
    sprintf(var_buff, "%.2f", var);     // 传递变量（十进制）
    u8g2_DrawStr(u8g2, x, y, var_buff); // 显示
}
/************************************ 初始化函数 ***********************************/

/********************************* 初始化数据处理函数 *******************************/

// 显示数值的初始化
void check_box_v_init(uint8_t *param)
{
    check_box.v = param;
}

// 多选框的初始化
void check_box_m_init(uint8_t *param)
{
    check_box.m = param;
}

// 单选框时的初始化
void check_box_s_init(uint8_t *param, uint8_t *param_p)
{
    check_box.s = param;
    check_box.s_p = param_p;
}

// 多选框处理函数
void check_box_m_select(uint8_t param)
{
    check_box.m[param] = !check_box.m[param];
    //  eeprom.change = true;
}

// 单选框处理函数
void check_box_s_select(uint8_t val, uint8_t pos)
{
    *check_box.s = val;
    *check_box.s_p = pos;
    //  eeprom.change = true;
}

// 弹窗数值初始化
void window_value_init(char title[], uint8_t select, uint8_t *value, uint8_t max, uint8_t min, uint8_t step, struct MENU *bg, uint8_t index)
{
    strcpy(win.title, title);
    win.select = select;
    win.value = value;
    win.max = max;
    win.min = min;
    win.step = step;
    win.bg = bg;
    win.index = index;
    ui.index = M_WINDOW;
    ui.state = S_WINDOW;
}
/*********************************** UI 初始化函数 *********************************/

// 在初始化EEPROM时，选择性初始化的默认设置
void ui_param_init()
{
    ui.param[DISP_BRI] = 255; // 屏幕亮度
    ui.param[TILE_ANI] = 30;  // 磁贴动画速度
    ui.param[LIST_ANI] = 60;  // 列表动画速度
    ui.param[WIN_ANI] = 25;   // 弹窗动画速度
    ui.param[SPOT_ANI] = 50;  // 聚光动画速度
    ui.param[TAG_ANI] = 60;   // 标签动画速度
    ui.param[FADE_ANI] = 30;  // 消失动画速度
    ui.param[BTN_SPT] = 25;   // 按键短按时长
    ui.param[BTN_LPT] = 150;  // 按键长按时长
    ui.param[TILE_UFD] = 1;   // 磁贴图标从头展开开关
    ui.param[LIST_UFD] = 1;   // 菜单列表从头展开开关
    ui.param[TILE_LOOP] = 0;  // 磁贴图标循环模式开关
    ui.param[LIST_LOOP] = 0;  // 菜单列表循环模式开关
    ui.param[WIN_BOK] = 0;    // 弹窗背景虚化开关
    ui.param[KNOB_DIR] = 0;   // 旋钮方向切换开关
    ui.param[DARK_MODE] = 1;  // 黑暗模式开关
}

// 列表类页面列表行数初始化，必须初始化的参数
void ui_init()
{
    ui.num[M_MAIN] = sizeof(main_menu) / sizeof(M_SELECT);
    ui.num[M_EDITOR] = sizeof(editor_menu) / sizeof(M_SELECT);
    ui.num[M_KNOB] = sizeof(knob_menu) / sizeof(M_SELECT);
    ui.num[M_KRF] = sizeof(krf_menu) / sizeof(M_SELECT);
    ui.num[M_KPF] = sizeof(kpf_menu) / sizeof(M_SELECT);
    ui.num[M_VOLT] = sizeof(volt_menu) / sizeof(M_SELECT);
    ui.num[M_SETTING] = sizeof(setting_menu) / sizeof(M_SELECT);
    ui.num[M_ABOUT] = sizeof(about_menu) / sizeof(M_SELECT);
}
/********************************* 分页面初始化函数 ********************************/

// 进入磁贴类时的初始化
void tile_param_init()
{
    ui.init = false;
    tile.icon_x = 0;
    tile.icon_x_trg = TILE_ICON_S;
    tile.icon_y = -TILE_ICON_H;
    tile.icon_y_trg = 0;
    tile.indi_x = 0;
    tile.indi_x_trg = TILE_INDI_W;
    tile.title_y = tile.title_y_calc;
    tile.title_y_trg = tile.title_y_trg_calc;
}

// 进入睡眠时的初始化
void sleep_param_init()
{
    u8g2_SetDrawColor(&u8g2, 0);
    u8g2_DrawBox(&u8g2, 0, 0, DISP_W, DISP_H);
    u8g2_SetPowerSave(&u8g2, 1);
    ui.state = S_NONE;
    ui.sleep = true;
    //  if (eeprom.change)
    //  {
    //    eeprom_write_all_data();
    //    eeprom.change = false;
    //  }
}

// 旋钮设置页初始化
void knob_param_init() { check_box_v_init(knob_param); }

// 旋钮旋转页初始化
void krf_param_init() { check_box_s_init(&knob_param[KNOB_ROT], &knob_param[KNOB_ROT_P]); }

// 旋钮点按页初始化
void kpf_param_init() { check_box_s_init(&knob_param[KNOB_COD], &knob_param[KNOB_COD_P]); }

// 电压测量页初始化
void volt_param_init()
{
    volt.text_bg_r = 0;
    volt.text_bg_r_trg = VOLT_TEXT_BG_W;
}

// 设置页初始化
void setting_param_init()
{
    check_box_v_init(ui.param);
    check_box_m_init(ui.param);
}
/********************************** 通用初始化函数 *********************************/

/*
  页面层级管理逻辑是，把所有页面都先当作列表类初始化，不是列表类按需求再初始化对应函数
  这样做会浪费一些资源，但跳转页面时只需要考虑页面层级，逻辑上更清晰，减少出错
*/

// 弹窗动画初始化
void window_param_init()
{
    win.bar = 0;
    win.y = WIN_Y;
    win.y_trg = win.u;
    ui.state = S_NONE;
}

// 进入更深层级时的初始化
void layer_init_in()
{
    ui.layer++;
    ui.init = 0;
    list.y = 0;
    list.y_trg = LIST_LINE_H;
    list.box_x = 0;
    list.box_y = 0;
    list.bar_y = 0;
    ui.state = S_FADE;
    switch (ui.index)
    {
    case M_MAIN:
        tile_param_init();
        break; // 睡眠进入主菜单，动画初始化
    case M_KNOB:
        knob_param_init();
        break; // 旋钮设置页，行末尾文字初始化
    case M_KRF:
        krf_param_init();
        break; // 旋钮旋转页，单选框初始化
    case M_KPF:
        kpf_param_init();
        break; // 旋钮点按页，单选框初始化
    case M_VOLT:
        volt_param_init();
        break; // 主菜单进入电压测量页，动画初始化
    case M_SETTING:
        setting_param_init();
        break; // 主菜单进入设置页，单选框初始化
    }
}

// 进入更浅层级时的初始化
void layer_init_out()
{
    ui.select[ui.layer] = 0;
    list.box_y_trg[ui.layer] = 0;
    ui.layer--;
    ui.init = 0;
    list.y = 0;
    list.y_trg = LIST_LINE_H;
    list.bar_y = 0;
    ui.state = S_FADE;
    switch (ui.index)
    {
    case M_SLEEP:
        sleep_param_init();
        break; // 主菜单进入睡眠页，检查是否需要写EEPROM
    case M_MAIN:
        tile_param_init();
        break; // 不管什么页面进入主菜单时，动画初始化
    }
}

/************************************* 动画函数 *************************************/

// 动画函数
void animation(float *a, float *a_trg, uint8_t n)
{
    if (fabs(*a - *a_trg) < 0.15)
        *a = *a_trg;
    if (*a != *a_trg)
        *a += (*a_trg - *a) / (ui.param[n] / 10.0);
}

// 消失函数
void fade()
{
    HAL_Delay(ui.param[FADE_ANI]);
    if (ui.param[DARK_MODE])
    {
        switch (ui.fade)
        {
        case 1:
            for (uint16_t i = 0; i < buf_len; ++i)
                if (i % 2 != 0)
                    buf_ptr[i] = buf_ptr[i] & 0xAA;
            break;
        case 2:
            for (uint16_t i = 0; i < buf_len; ++i)
                if (i % 2 != 0)
                    buf_ptr[i] = buf_ptr[i] & 0x00;
            break;
        case 3:
            for (uint16_t i = 0; i < buf_len; ++i)
                if (i % 2 == 0)
                    buf_ptr[i] = buf_ptr[i] & 0x55;
            break;
        case 4:
            for (uint16_t i = 0; i < buf_len; ++i)
                if (i % 2 == 0)
                    buf_ptr[i] = buf_ptr[i] & 0x00;
            break;
        default:
            ui.state = S_NONE;
            ui.fade = 0;
            break;
        }
    }
    else
    {
        switch (ui.fade)
        {
        case 1:
            for (uint16_t i = 0; i < buf_len; ++i)
                if (i % 2 != 0)
                    buf_ptr[i] = buf_ptr[i] | 0xAA;
            break;
        case 2:
            for (uint16_t i = 0; i < buf_len; ++i)
                if (i % 2 != 0)
                    buf_ptr[i] = buf_ptr[i] | 0x00;
            break;
        case 3:
            for (uint16_t i = 0; i < buf_len; ++i)
                if (i % 2 == 0)
                    buf_ptr[i] = buf_ptr[i] | 0x55;
            break;
        case 4:
            for (uint16_t i = 0; i < buf_len; ++i)
                if (i % 2 == 0)
                    buf_ptr[i] = buf_ptr[i] | 0x00;
            break;
        default:
            ui.state = S_NONE;
            ui.fade = 0;
            break;
        }
    }
    ui.fade++;
}
/************************************* 显示函数 *************************************/

// 磁贴类页面通用显示函数
void tile_show(struct MENU arr_1[], const uint8_t icon_pic[][120])
{
    // 计算动画过渡值
    animation(&tile.icon_x, &tile.icon_x_trg, TILE_ANI);
    animation(&tile.icon_y, &tile.icon_y_trg, TILE_ANI);
    animation(&tile.indi_x, &tile.indi_x_trg, TILE_ANI);
    animation(&tile.title_y, &tile.title_y_trg, TILE_ANI);

    // 设置大标题的颜色，0透显，1实显，2反色，这里用实显
    u8g2_SetDrawColor(&u8g2, 1);

    // 绘制大标题
    u8g2_SetFont(&u8g2, TILE_B_FONT);
    u8g2_DrawStr(&u8g2, ((DISP_W - TILE_INDI_W) - u8g2_GetStrWidth(&u8g2, arr_1[ui.select[ui.layer]].m_select)) / 2 + TILE_INDI_W, tile.title_y, arr_1[ui.select[ui.layer]].m_select);

    // 绘制大标题指示器
    u8g2_DrawBox(&u8g2, 0, TILE_ICON_S, tile.indi_x, TILE_INDI_H);

    // 绘制图标
    if (!ui.init)
    {
        for (uint8_t i = 0; i < ui.num[ui.index]; ++i)
        {
            if (ui.param[TILE_UFD])
                tile.temp = (DISP_W - TILE_ICON_W) / 2 + i * tile.icon_x - TILE_ICON_S * ui.select[ui.layer];
            else
                tile.temp = (DISP_W - TILE_ICON_W) / 2 + (i - ui.select[ui.layer]) * tile.icon_x;
            u8g2_DrawXBMP(&u8g2, tile.temp, (int16_t)tile.icon_y, TILE_ICON_W, TILE_ICON_H, icon_pic[i]);
        }
        if (tile.icon_x == tile.icon_x_trg)
        {
            ui.init = true;
            tile.icon_x = tile.icon_x_trg = -ui.select[ui.layer] * TILE_ICON_S;
        }
    }
    else
        for (uint8_t i = 0; i < ui.num[ui.index]; ++i)
            u8g2_DrawXBMP(&u8g2, (DISP_W - TILE_ICON_W) / 2 + (int16_t)tile.icon_x + i * TILE_ICON_S, 0, TILE_ICON_W, TILE_ICON_H, icon_pic[i]);

    // 反转屏幕内元素颜色，白天模式遮罩
    u8g2_SetDrawColor(&u8g2, 2);
    if (!ui.param[DARK_MODE])
        u8g2_DrawBox(&u8g2, 0, 0, DISP_W, DISP_H);
}
/*************** 根据列表每行开头符号，判断每行尾部是否绘制以及绘制什么内容 *************/

// 列表显示数值
void list_draw_value(int n)
{
    // u8g2.print(check_box.v[n - 1]);
    Printint(&u8g2, CHECK_BOX_L_S, list.temp + LIST_TEXT_H + LIST_TEXT_S, check_box.v[n - 1]);
}

// 绘制外框
void list_draw_check_box_frame() { u8g2_DrawRFrame(&u8g2, CHECK_BOX_L_S, list.temp + CHECK_BOX_U_S, CHECK_BOX_F_W, CHECK_BOX_F_H, 1); }

// 绘制框里面的点
void list_draw_check_box_dot() { u8g2_DrawBox(&u8g2, CHECK_BOX_L_S + CHECK_BOX_D_S + 1, list.temp + CHECK_BOX_U_S + CHECK_BOX_D_S + 1, CHECK_BOX_F_W - (CHECK_BOX_D_S + 1) * 2, CHECK_BOX_F_H - (CHECK_BOX_D_S + 1) * 2); }

// 列表显示旋钮功能
void list_draw_krf(int n)
{
    switch (check_box.v[n - 1])
    {
    case 0:
        //        u8g2.print("OFF");
        u8g2_DrawStr(&u8g2, CHECK_BOX_L_S, list.temp + LIST_TEXT_H + LIST_TEXT_S, "OFF");
        break;
    case 1:
        //        u8g2.print("VOL");
        u8g2_DrawStr(&u8g2, CHECK_BOX_L_S, list.temp + LIST_TEXT_H + LIST_TEXT_S, "VOL");
        break;
    case 2:
        //        u8g2.print("BRI");
        u8g2_DrawStr(&u8g2, CHECK_BOX_L_S, list.temp + LIST_TEXT_H + LIST_TEXT_S, "BRI");
        break;
    }
}

// 列表显示按键键值
void list_draw_kpf(int n)
{
    if (check_box.v[n - 1] == 0)
        //        u8g2.print("OFF");
        u8g2_DrawStr(&u8g2, CHECK_BOX_L_S, list.temp + LIST_TEXT_H + LIST_TEXT_S, "OFF");
    else if (check_box.v[n - 1] <= 90)
        //        u8g2.print((char)check_box.v[n - 1]);
        Printint(&u8g2, CHECK_BOX_L_S, list.temp + LIST_TEXT_H + LIST_TEXT_S, (char)check_box.v[n - 1]);
    else
        //        u8g2.print("?");
        u8g2_DrawStr(&u8g2, CHECK_BOX_L_S, list.temp + LIST_TEXT_H + LIST_TEXT_S, "?");
}
// 判断列表尾部内容
void list_draw_text_and_check_box(struct MENU arr[], int i)
{
    u8g2_DrawStr(&u8g2, LIST_TEXT_S, list.temp + LIST_TEXT_H + LIST_TEXT_S, arr[i].m_select);
    //  u8g2.SetCursor(CHECK_BOX_L_S, list.temp + LIST_TEXT_H + LIST_TEXT_S);
    switch (arr[i].m_select[0])
    {
    case '~':
        list_draw_value(i);
        break;
    case '+':
        list_draw_check_box_frame();
        if (check_box.m[i - 1] == 1)
            list_draw_check_box_dot();
        break;
    case '=':
        list_draw_check_box_frame();
        if (*check_box.s_p == i)
            list_draw_check_box_dot();
        break;
    case '#':
        list_draw_krf(i);
        break;
    case '$':
        list_draw_kpf(i);
        break;
    }
}
/******************************** 列表显示函数 **************************************/

// 列表类页面通用显示函数
void list_show(struct MENU arr[], uint8_t ui_index)
{
    // 更新动画目标值
    u8g2_SetFont(&u8g2, LIST_FONT);
    list.box_x_trg = u8g2_GetStrWidth(&u8g2, arr[ui.select[ui.layer]].m_select) + LIST_TEXT_S * 2;
    list.bar_y_trg = ceil((ui.select[ui.layer]) * ((float)DISP_H / (ui.num[ui_index] - 1)));

    // 计算动画过渡值
    animation(&list.y, &list.y_trg, LIST_ANI);
    animation(&list.box_x, &list.box_x_trg, LIST_ANI);
    animation(&list.box_y, &list.box_y_trg[ui.layer], LIST_ANI);
    animation(&list.bar_y, &list.bar_y_trg, LIST_ANI);

    // 检查循环动画是否结束
    if (list.loop && list.box_y == list.box_y_trg[ui.layer])
        list.loop = false;

    // 设置文字和进度条颜色，0透显，1实显，2反色，这里都用实显
    u8g2_SetDrawColor(&u8g2, 1);

    // 绘制进度条
    u8g2_DrawHLine(&u8g2, DISP_W - LIST_BAR_W, 0, LIST_BAR_W);
    u8g2_DrawHLine(&u8g2, DISP_W - LIST_BAR_W, DISP_H - 1, LIST_BAR_W);
    u8g2_DrawVLine(&u8g2, DISP_W - ceil((float)LIST_BAR_W / 2), 0, DISP_H);
    u8g2_DrawBox(&u8g2, DISP_W - LIST_BAR_W, 0, LIST_BAR_W, list.bar_y);

    // 绘制列表文字
    if (!ui.init)
    {
        for (int i = 0; i < ui.num[ui_index]; ++i)
        {
            if (ui.param[LIST_UFD])
                list.temp = i * list.y - LIST_LINE_H * ui.select[ui.layer] + list.box_y_trg[ui.layer];
            else
                list.temp = (i - ui.select[ui.layer]) * list.y + list.box_y_trg[ui.layer];
            list_draw_text_and_check_box(arr, i);
        }
        if (list.y == list.y_trg)
        {
            ui.init = true;
            list.y = list.y_trg = -LIST_LINE_H * ui.select[ui.layer] + list.box_y_trg[ui.layer];
        }
    }
    else
        for (int i = 0; i < ui.num[ui_index]; ++i)
        {
            list.temp = LIST_LINE_H * i + list.y;
            list_draw_text_and_check_box(arr, i);
        }

    // 绘制文字选择框，0透显，1实显，2反色，这里用反色
    u8g2_SetDrawColor(&u8g2, 2);
    u8g2_DrawRBox(&u8g2, 0, list.box_y, list.box_x, LIST_LINE_H, LIST_BOX_R);

    // 反转屏幕内元素颜色，白天模式遮罩，在这里屏蔽有列表参与的页面，使遮罩作用在那个页面上
    if (!ui.param[DARK_MODE])
    {
        u8g2_DrawBox(&u8g2, 0, 0, DISP_W, DISP_H);
        switch (ui.index)
        {
        case M_WINDOW:
        case M_VOLT:
            u8g2_DrawBox(&u8g2, 0, 0, DISP_W, DISP_H);
        }
    }
}
// 电压页面显示函数
void volt_show()
{
    // 使用列表类显示选项
    list_show(volt_menu, M_VOLT);

    // 计算动画过渡值
    animation(&volt.text_bg_r, &volt.text_bg_r_trg, TAG_ANI);

    // 设置曲线颜色，0透显，1实显，2反色，这里用实显
    u8g2_SetDrawColor(&u8g2, 1);

    // 绘制电压曲线和外框
    volt.val = 0;
    u8g2_DrawFrame(&u8g2, WAVE_BOX_L_S, 0, WAVE_BOX_W, WAVE_BOX_H);
    u8g2_DrawFrame(&u8g2, WAVE_BOX_L_S + 1, 1, WAVE_BOX_W - 2, WAVE_BOX_H - 2);
    if (list.box_y == list.box_y_trg[ui.layer] && list.y == list.y_trg)
    {
        for (int i = 0; i < WAVE_SAMPLE * WAVE_W; i++)
            //		 volt.ch0_adc[i] = volt.val = analogRead(analog_pin[ui.select[ui.layer]]);
            volt.ch0_adc[i] = volt.val = ADC_Value;
        for (int i = 1; i < WAVE_W - 1; i++)
        {
            //			volt.ch0_wave[i] = map(volt.ch0_adc[int(5 * i)], 0, 4095, WAVE_MAX, WAVE_MIN); // 映射map(num，旧区间初值，旧区间终值，新区间初值，新区间终值)；
            volt.ch0_wave[i] = volt.ch0_adc[(int)(5 * i)] / 178 + 4;
            u8g2_DrawLine(&u8g2, WAVE_L + i - 1, WAVE_U + volt.ch0_wave[i - 1], WAVE_L + i, WAVE_U + volt.ch0_wave[i]);
            volt.ch0_wave[i - 1] = volt.ch0_wave[i];
        }
    }

    // 绘制电压值
    u8g2_SetFontDirection(&u8g2, 0);
    u8g2_SetFont(&u8g2, VOLT_FONT);
    Printfloat(&u8g2, 39, DISP_H - 6, volt.val / 4096.0 * 3.3);
    //    u8g2_DrawStr(&u8g2, 39, DISP_H - 6, "        V");
    u8g2_DrawStr(&u8g2, 87, DISP_H - 6, "V");

    // 绘制列表选择框和电压文字背景
    u8g2_SetDrawColor(&u8g2, 2);
    u8g2_DrawBox(&u8g2, VOLT_TEXT_BG_L_S, DISP_H - VOLT_TEXT_BG_H, volt.text_bg_r, VOLT_TEXT_BG_H);

    // 反转屏幕内元素颜色，白天模式遮罩
    if (!ui.param[DARK_MODE])
        u8g2_DrawBox(&u8g2, 0, 0, DISP_W, DISP_H);
}
// 弹窗通用显示函数
void window_show()
{
    // 绘制背景列表，根据开关判断背景是否要虚化
    list_show(win.bg, win.index);
    if (ui.param[WIN_BOK])
        for (uint16_t i = 0; i < buf_len; ++i)
            buf_ptr[i] = buf_ptr[i] & (i % 2 == 0 ? 0x55 : 0xAA);

    // 更新动画目标值
    u8g2_SetFont(&u8g2, WIN_FONT);
    win.bar_trg = (float)(*win.value - win.min) / (float)(win.max - win.min) * (WIN_BAR_W - 4);

    // 计算动画过渡值
    animation(&win.bar, &win.bar_trg, WIN_ANI);
    animation(&win.y, &win.y_trg, WIN_ANI);

    // 绘制窗口
    u8g2_SetDrawColor(&u8g2, 0);
    u8g2_DrawRBox(&u8g2, win.l, (int16_t)win.y, WIN_W, WIN_H, 2); // 绘制外框背景
    u8g2_SetDrawColor(&u8g2, 1);
    u8g2_DrawRFrame(&u8g2, win.l, (int16_t)win.y, WIN_W, WIN_H, 2);                  // 绘制外框描边
    u8g2_DrawRFrame(&u8g2, win.l + 5, (int16_t)win.y + 20, WIN_BAR_W, WIN_BAR_H, 1); // 绘制进度条外框
    u8g2_DrawBox(&u8g2, win.l + 7, (int16_t)win.y + 22, win.bar, WIN_BAR_H - 4);     // 绘制进度条
    u8g2_DrawStr(&u8g2, win.l + 5, (int16_t)win.y + 14, win.title);                  // 绘制标题
    Printint(&u8g2, win.l + 78, (int16_t)win.y + 14, *win.value);                    // 绘制当前值

    // 需要在窗口修改参数时立即见效的函数
    if (!strcmp(win.title, "Disp Bri"))
        u8g2_SetContrast(&u8g2, ui.param[DISP_BRI]);

    // 反转屏幕内元素颜色，白天模式遮罩
    u8g2_SetDrawColor(&u8g2, 2);
    if (!ui.param[DARK_MODE])
        u8g2_DrawBox(&u8g2, 0, 0, DISP_W, DISP_H);
}

/************************************* 处理函数 *************************************/

/*********************************** 通用处理函数 ***********************************/

// 磁贴类页面旋转时判断通用函数
void tile_rotate_switch()
{
    switch (btn.id)
    {
    case BTN_ID_CC:
        if (ui.init)
        {
            if (ui.select[ui.layer] > 0)
            {
                ui.select[ui.layer] -= 1;
                tile.icon_x_trg += TILE_ICON_S;
                tile.select_flag = false;
            }
            else
            {
                if (ui.param[TILE_LOOP])
                {
                    ui.select[ui.layer] = ui.num[ui.index] - 1;
                    tile.icon_x_trg = -TILE_ICON_S * (ui.num[ui.index] - 1);
                    break;
                }
                else
                    tile.select_flag = true;
            }
        }
        break;

    case BTN_ID_CW:
        if (ui.init)
        {
            if (ui.select[ui.layer] < (ui.num[ui.index] - 1))
            {
                ui.select[ui.layer] += 1;
                tile.icon_x_trg -= TILE_ICON_S;
                tile.select_flag = false;
            }
            else
            {
                if (ui.param[TILE_LOOP])
                {
                    ui.select[ui.layer] = 0;
                    tile.icon_x_trg = 0;
                    break;
                }
                else
                    tile.select_flag = true;
            }
        }
        break;
    }
}

// 列表类页面旋转时判断通用函数
void list_rotate_switch()
{
    if (!list.loop)
    {
        switch (btn.id)
        {
        case BTN_ID_CC:
            if (ui.select[ui.layer] == 0)
            {
                if (ui.param[LIST_LOOP] && ui.init)
                {
                    list.loop = true;
                    ui.select[ui.layer] = ui.num[ui.index] - 1;
                    if (ui.num[ui.index] > list.line_n)
                    {
                        list.box_y_trg[ui.layer] = DISP_H - LIST_LINE_H;
                        list.y_trg = DISP_H - ui.num[ui.index] * LIST_LINE_H;
                    }
                    else
                        list.box_y_trg[ui.layer] = (ui.num[ui.index] - 1) * LIST_LINE_H;
                    break;
                }
                else
                    break;
            }
            if (ui.init)
            {
                ui.select[ui.layer] -= 1;
                if (ui.select[ui.layer] < -(list.y_trg / LIST_LINE_H))
                {
                    if (!(DISP_H % LIST_LINE_H))
                        list.y_trg += LIST_LINE_H;
                    else
                    {
                        if (list.box_y_trg[ui.layer] == DISP_H - LIST_LINE_H * list.line_n)
                        {
                            list.y_trg += (list.line_n + 1) * LIST_LINE_H - DISP_H;
                            list.box_y_trg[ui.layer] = 0;
                        }
                        else if (list.box_y_trg[ui.layer] == LIST_LINE_H)
                        {
                            list.box_y_trg[ui.layer] = 0;
                        }
                        else
                            list.y_trg += LIST_LINE_H;
                    }
                }
                else
                    list.box_y_trg[ui.layer] -= LIST_LINE_H;
                break;
            }

        case BTN_ID_CW:
            if (ui.select[ui.layer] == (ui.num[ui.index] - 1))
            {
                if (ui.param[LIST_LOOP] && ui.init)
                {
                    list.loop = true;
                    ui.select[ui.layer] = 0;
                    list.y_trg = 0;
                    list.box_y_trg[ui.layer] = 0;
                    break;
                }
                else
                    break;
            }
            if (ui.init)
            {
                ui.select[ui.layer] += 1;
                if ((ui.select[ui.layer] + 1) > (list.line_n - list.y_trg / LIST_LINE_H))
                {
                    if (!(DISP_H % LIST_LINE_H))
                        list.y_trg -= LIST_LINE_H;
                    else
                    {
                        if (list.box_y_trg[ui.layer] == LIST_LINE_H * (list.line_n - 1))
                        {
                            list.y_trg -= (list.line_n + 1) * LIST_LINE_H - DISP_H;
                            list.box_y_trg[ui.layer] = DISP_H - LIST_LINE_H;
                        }
                        else if (list.box_y_trg[ui.layer] == DISP_H - LIST_LINE_H * 2)
                        {
                            list.box_y_trg[ui.layer] = DISP_H - LIST_LINE_H;
                        }
                        else
                            list.y_trg -= LIST_LINE_H;
                    }
                }
                else
                    list.box_y_trg[ui.layer] += LIST_LINE_H;
                break;
            }
            break;
        }
    }
}

// 弹窗通用处理函数
void window_proc()
{
    window_show();
    if (win.y == WIN_Y_TRG)
        ui.index = win.index;
    if (btn.pressed && win.y == win.y_trg && win.y != WIN_Y_TRG)
    {
        btn.pressed = false;
        switch (btn.id)
        {
        case BTN_ID_CW:
            if (*win.value < win.max)
                *win.value += win.step;
            //        eeprom.change = true;
            break;
        case BTN_ID_CC:
            if (*win.value > win.min)
                *win.value -= win.step;
            //        eeprom.change = true;
            break;
        case BTN_ID_SP:
        case BTN_ID_LP:
            win.y_trg = WIN_Y_TRG;
            break;
        }
    }
}

/********************************** 分页面处理函数 **********************************/

// 睡眠页面处理函数
void sleep_proc()
{
    while (ui.sleep)
    {
        // 睡眠时循环执行的函数

        // 睡眠时需要扫描旋钮才能退出睡眠
        btn_scan();

        // 当旋钮有动作时
        if (btn.pressed)
        {
            btn.pressed = false;
            switch (btn.id)
            {

            // 睡眠时顺时针旋转执行的函数
            case BTN_ID_CW:
                switch (knob_param[KNOB_ROT])
                {
                case KNOB_ROT_VOL:
                    //                Consumer.press(HIDConsumer::VOLUME_UP);       Consumer.release();
                    break;
                case KNOB_ROT_BRI:
                    //                Consumer.press(HIDConsumer::BRIGHTNESS_UP);   Consumer.release();
                    break;
                }
                break;

            // 睡眠时逆时针旋转执行的函数
            case BTN_ID_CC:
                switch (knob_param[KNOB_ROT])
                {
                case KNOB_ROT_VOL:
                    //                Consumer.press(HIDConsumer::VOLUME_DOWN);     Consumer.release();
                    break;
                case KNOB_ROT_BRI:
                    //                Consumer.press(HIDConsumer::BRIGHTNESS_DOWN); Consumer.release();
                    break;
                }
                break;

            // 睡眠时短按执行的函数
            case BTN_ID_SP:
                //            Keyboard.press(knob_param[KNOB_COD]);  Keyboard.release(knob_param[KNOB_COD]);
                break;

            // 睡眠时长按执行的函数
            case BTN_ID_LP:
                ui.index = M_MAIN;
                ui.state = S_LAYER_IN;
                u8g2_SetPowerSave(&u8g2, 0);
                ui.sleep = false;
                break;
            }
        }
    }
}

// 主菜单处理函数，磁贴类模板
void main_proc()
{
    tile_show(main_menu, main_icon_pic);
    if (btn.pressed)
    {
        btn.pressed = false;
        switch (btn.id)
        {
        case BTN_ID_CW:
        case BTN_ID_CC:
            tile_rotate_switch();
            break;
        case BTN_ID_SP:
            switch (ui.select[ui.layer])
            {
            case 0:
                ui.index = M_SLEEP;
                ui.state = S_LAYER_OUT;
                break;
            case 1:
                ui.index = M_EDITOR;
                ui.state = S_LAYER_IN;
                break;
            case 2:
                ui.index = M_VOLT;
                ui.state = S_LAYER_IN;
                break;
            case 3:
                ui.index = M_SETTING;
                ui.state = S_LAYER_IN;
                break;
            }
        }
        if (!tile.select_flag && ui.init)
        {
            tile.indi_x = 0;
            tile.title_y = tile.title_y_calc;
        }
    }
}
// 编辑器菜单处理函数
void editor_proc()
{
    list_show(editor_menu, M_EDITOR);
    if (btn.pressed)
    {
        btn.pressed = false;
        switch (btn.id)
        {
        case BTN_ID_CW:
        case BTN_ID_CC:
            list_rotate_switch();
            break;
        case BTN_ID_LP:
            ui.select[ui.layer] = 0;
        case BTN_ID_SP:
            switch (ui.select[ui.layer])
            {

            case 0:
                ui.index = M_MAIN;
                ui.state = S_LAYER_OUT;
                break;
            case 11:
                ui.index = M_KNOB;
                ui.state = S_LAYER_IN;
                break;
            }
        }
    }
}
// 旋钮编辑菜单处理函数
void knob_proc()
{
    list_show(knob_menu, M_KNOB);
    if (btn.pressed)
    {
        btn.pressed = false;
        switch (btn.id)
        {
        case BTN_ID_CW:
        case BTN_ID_CC:
            list_rotate_switch();
            break;
        case BTN_ID_LP:
            ui.select[ui.layer] = 0;
        case BTN_ID_SP:
            switch (ui.select[ui.layer])
            {
            case 0:
                ui.index = M_EDITOR;
                ui.state = S_LAYER_OUT;
                break;
            case 1:
                ui.index = M_KRF;
                ui.state = S_LAYER_IN;
                check_box_s_init(&knob_param[KNOB_ROT], &knob_param[KNOB_ROT_P]);
                break;
            case 2:
                ui.index = M_KPF;
                ui.state = S_LAYER_IN;
                check_box_s_init(&knob_param[KNOB_COD], &knob_param[KNOB_COD_P]);
                break;
            }
        }
    }
}

// 旋钮旋转功能菜单处理函数
void krf_proc()
{
    list_show(krf_menu, M_KRF);
    if (btn.pressed)
    {
        btn.pressed = false;
        switch (btn.id)
        {
        case BTN_ID_CW:
        case BTN_ID_CC:
            list_rotate_switch();
            break;
        case BTN_ID_LP:
            ui.select[ui.layer] = 0;
        case BTN_ID_SP:
            switch (ui.select[ui.layer])
            {
            case 0:
                ui.index = M_KNOB;
                ui.state = S_LAYER_OUT;
                break;
            case 1:
                break;
            case 2:
                check_box_s_select(KNOB_DISABLE, ui.select[ui.layer]);
                break;
            case 3:
                break;
            case 4:
                check_box_s_select(KNOB_ROT_VOL, ui.select[ui.layer]);
                break;
            case 5:
                check_box_s_select(KNOB_ROT_BRI, ui.select[ui.layer]);
                break;
            case 6:
                break;
            }
        }
    }
}

// 旋钮点按功能菜单处理函数
void kpf_proc()
{
    list_show(kpf_menu, M_KPF);
    if (btn.pressed)
    {
        btn.pressed = false;
        switch (btn.id)
        {
        case BTN_ID_CW:
        case BTN_ID_CC:
            list_rotate_switch();
            break;
        case BTN_ID_LP:
            ui.select[ui.layer] = 0;
        case BTN_ID_SP:
            switch (ui.select[ui.layer])
            {

            case 0:
                ui.index = M_KNOB;
                ui.state = S_LAYER_OUT;
                break;
            case 1:
                break;
            case 2:
                check_box_s_select(KNOB_DISABLE, ui.select[ui.layer]);
                break;
            case 3:
                break;
            case 4:
                check_box_s_select('A', ui.select[ui.layer]);
                break;
            case 5:
                check_box_s_select('B', ui.select[ui.layer]);
                break;
            case 6:
                check_box_s_select('C', ui.select[ui.layer]);
                break;
            case 7:
                check_box_s_select('D', ui.select[ui.layer]);
                break;
            case 8:
                check_box_s_select('E', ui.select[ui.layer]);
                break;
            case 9:
                check_box_s_select('F', ui.select[ui.layer]);
                break;
            case 10:
                check_box_s_select('G', ui.select[ui.layer]);
                break;
            case 11:
                check_box_s_select('H', ui.select[ui.layer]);
                break;
            case 12:
                check_box_s_select('I', ui.select[ui.layer]);
                break;
            case 13:
                check_box_s_select('J', ui.select[ui.layer]);
                break;
            case 14:
                check_box_s_select('K', ui.select[ui.layer]);
                break;
            case 15:
                check_box_s_select('L', ui.select[ui.layer]);
                break;
            case 16:
                check_box_s_select('M', ui.select[ui.layer]);
                break;
            case 17:
                check_box_s_select('N', ui.select[ui.layer]);
                break;
            case 18:
                check_box_s_select('O', ui.select[ui.layer]);
                break;
            case 19:
                check_box_s_select('P', ui.select[ui.layer]);
                break;
            case 20:
                check_box_s_select('Q', ui.select[ui.layer]);
                break;
            case 21:
                check_box_s_select('R', ui.select[ui.layer]);
                break;
            case 22:
                check_box_s_select('S', ui.select[ui.layer]);
                break;
            case 23:
                check_box_s_select('T', ui.select[ui.layer]);
                break;
            case 24:
                check_box_s_select('U', ui.select[ui.layer]);
                break;
            case 25:
                check_box_s_select('V', ui.select[ui.layer]);
                break;
            case 26:
                check_box_s_select('W', ui.select[ui.layer]);
                break;
            case 27:
                check_box_s_select('X', ui.select[ui.layer]);
                break;
            case 28:
                check_box_s_select('Y', ui.select[ui.layer]);
                break;
            case 29:
                check_box_s_select('Z', ui.select[ui.layer]);
                break;
            case 30:
                break;
            case 31:
                check_box_s_select('0', ui.select[ui.layer]);
                break;
            case 32:
                check_box_s_select('1', ui.select[ui.layer]);
                break;
            case 33:
                check_box_s_select('2', ui.select[ui.layer]);
                break;
            case 34:
                check_box_s_select('3', ui.select[ui.layer]);
                break;
            case 35:
                check_box_s_select('4', ui.select[ui.layer]);
                break;
            case 36:
                check_box_s_select('5', ui.select[ui.layer]);
                break;
            case 37:
                check_box_s_select('6', ui.select[ui.layer]);
                break;
            case 38:
                check_box_s_select('7', ui.select[ui.layer]);
                break;
            case 39:
                check_box_s_select('8', ui.select[ui.layer]);
                break;
            case 40:
                check_box_s_select('9', ui.select[ui.layer]);
                break;
            case 41:
                break;
                //        case 42:  check_box_s_select( KEY_ESC, ui.select[ui.layer]); break;
                //        case 43:  check_box_s_select( KEY_F1,  ui.select[ui.layer]); break;
                //        case 44:  check_box_s_select( KEY_F2,  ui.select[ui.layer]); break;
                //        case 45:  check_box_s_select( KEY_F3,  ui.select[ui.layer]); break;
                //        case 46:  check_box_s_select( KEY_F4,  ui.select[ui.layer]); break;
                //        case 47:  check_box_s_select( KEY_F5,  ui.select[ui.layer]); break;
                //        case 48:  check_box_s_select( KEY_F6,  ui.select[ui.layer]); break;
                //        case 49:  check_box_s_select( KEY_F7,  ui.select[ui.layer]); break;
                //        case 50:  check_box_s_select( KEY_F8,  ui.select[ui.layer]); break;
                //        case 51:  check_box_s_select( KEY_F9,  ui.select[ui.layer]); break;
                //        case 52:  check_box_s_select( KEY_F10, ui.select[ui.layer]); break;
                //        case 53:  check_box_s_select( KEY_F11, ui.select[ui.layer]); break;
                //        case 54:  check_box_s_select( KEY_F12, ui.select[ui.layer]); break;
            case 55:
                break;
                //        case 56:  check_box_s_select( KEY_LEFT_CTRL,   ui.select[ui.layer]); break;
                //        case 57:  check_box_s_select( KEY_LEFT_SHIFT,  ui.select[ui.layer]); break;
                //        case 58:  check_box_s_select( KEY_LEFT_ALT,    ui.select[ui.layer]); break;
                //        case 59:  check_box_s_select( KEY_LEFT_GUI,    ui.select[ui.layer]); break;
                //        case 60:  check_box_s_select( KEY_RIGHT_CTRL,  ui.select[ui.layer]); break;
                //        case 61:  check_box_s_select( KEY_RIGHT_SHIFT, ui.select[ui.layer]); break;
                //        case 62:  check_box_s_select( KEY_RIGHT_ALT,   ui.select[ui.layer]); break;
                //        case 63:  check_box_s_select( KEY_RIGHT_GUI,   ui.select[ui.layer]); break;
            case 64:
                break;
                //        case 65:  check_box_s_select( KEY_CAPS_LOCK,   ui.select[ui.layer]); break;
                //        case 66:  check_box_s_select( KEY_BACKSPACE,   ui.select[ui.layer]); break;
                //        case 67:  check_box_s_select( KEY_RETURN,      ui.select[ui.layer]); break;
                //        case 68:  check_box_s_select( KEY_INSERT,      ui.select[ui.layer]); break;
                //        case 69:  check_box_s_select( KEY_DELETE,      ui.select[ui.layer]); break;
                //        case 70:  check_box_s_select( KEY_TAB,         ui.select[ui.layer]); break;
            case 71:
                break;
                //        case 72:  check_box_s_select( KEY_HOME,        ui.select[ui.layer]); break;
                //        case 73:  check_box_s_select( KEY_END,         ui.select[ui.layer]); break;
                //        case 74:  check_box_s_select( KEY_PAGE_UP,     ui.select[ui.layer]); break;
                //        case 75:  check_box_s_select( KEY_PAGE_DOWN,   ui.select[ui.layer]); break;
            case 76:
                break;
                //        case 77:  check_box_s_select( KEY_UP_ARROW,    ui.select[ui.layer]); break;
                //        case 78:  check_box_s_select( KEY_DOWN_ARROW,  ui.select[ui.layer]); break;
                //        case 79:  check_box_s_select( KEY_LEFT_ARROW,  ui.select[ui.layer]); break;
                //        case 80:  check_box_s_select( KEY_RIGHT_ARROW, ui.select[ui.layer]); break;
            case 81:
                break;
            }
        }
    }
}

// 电压测量页处理函数
void volt_proc()
{
    volt_show();
    if (btn.pressed)
    {
        btn.pressed = false;
        switch (btn.id)
        {
        case BTN_ID_CW:
        case BTN_ID_CC:
            list_rotate_switch();
            break;

        case BTN_ID_SP:
        case BTN_ID_LP:
            ui.index = M_MAIN;
            ui.state = S_LAYER_OUT;
            break;
        }
    }
}

// 设置菜单处理函数，多选框列表类模板，弹窗模板
void setting_proc()
{
    list_show(setting_menu, M_SETTING);
    if (btn.pressed)
    {
        btn.pressed = false;
        switch (btn.id)
        {
        case BTN_ID_CW:
        case BTN_ID_CC:
            list_rotate_switch();
            break;
        case BTN_ID_LP:
            ui.select[ui.layer] = 0;
        case BTN_ID_SP:
            switch (ui.select[ui.layer])
            {

            // 返回更浅层级，长按被当作选择这一项，也是执行这一行
            case 0:
                ui.index = M_MAIN;
                ui.state = S_LAYER_OUT;
                break;

            // 弹出窗口，参数初始化：标题，参数名，参数值，最大值，最小值，步长，背景列表名，背景列表标签
            case 1:
                window_value_init("Disp Bri", DISP_BRI, &ui.param[DISP_BRI], 255, 0, 5, setting_menu, M_SETTING);
                break;
            case 2:
                window_value_init("Tile Ani", TILE_ANI, &ui.param[TILE_ANI], 100, 10, 1, setting_menu, M_SETTING);
                break;
            case 3:
                window_value_init("List Ani", LIST_ANI, &ui.param[LIST_ANI], 100, 10, 1, setting_menu, M_SETTING);
                break;
            case 4:
                window_value_init("Win Ani", WIN_ANI, &ui.param[WIN_ANI], 100, 10, 1, setting_menu, M_SETTING);
                break;
            case 5:
                window_value_init("Spot Ani", SPOT_ANI, &ui.param[SPOT_ANI], 100, 10, 1, setting_menu, M_SETTING);
                break;
            case 6:
                window_value_init("Tag Ani", TAG_ANI, &ui.param[TAG_ANI], 100, 10, 1, setting_menu, M_SETTING);
                break;
            case 7:
                window_value_init("Fade Ani", FADE_ANI, &ui.param[FADE_ANI], 255, 0, 1, setting_menu, M_SETTING);
                break;
            case 8:
                window_value_init("Btn SPT", BTN_SPT, &ui.param[BTN_SPT], 255, 0, 1, setting_menu, M_SETTING);
                break;
            case 9:
                window_value_init("Btn LPT", BTN_LPT, &ui.param[BTN_LPT], 255, 0, 1, setting_menu, M_SETTING);
                break;

            // 多选框
            case 10:
                check_box_m_select(TILE_UFD);
                break;
            case 11:
                check_box_m_select(LIST_UFD);
                break;
            case 12:
                check_box_m_select(TILE_LOOP);
                break;
            case 13:
                check_box_m_select(LIST_LOOP);
                break;
            case 14:
                check_box_m_select(WIN_BOK);
                break;
            case 15:
                check_box_m_select(KNOB_DIR);
                break;
            case 16:
                check_box_m_select(DARK_MODE);
                break;

            // 关于本机
            case 17:
                ui.index = M_ABOUT;
                ui.state = S_LAYER_IN;
                break;
            }
        }
    }
}

// 关于本机页
void about_proc()
{
    list_show(about_menu, M_ABOUT);
    if (btn.pressed)
    {
        btn.pressed = false;
        switch (btn.id)
        {
        case BTN_ID_CW:
        case BTN_ID_CC:
            list_rotate_switch();
            break;
        case BTN_ID_LP:
            ui.select[ui.layer] = 0;
        case BTN_ID_SP:
            switch (ui.select[ui.layer])
            {

            case 0:
                ui.index = M_SETTING;
                ui.state = S_LAYER_OUT;
                break;
            }
        }
    }
}

// 总的UI进程
void ui_proc()
{
    u8g2_SendBuffer(&u8g2);
    switch (ui.state)
    {
    case S_FADE:
        fade();
        break; // 转场动画
    case S_WINDOW:
        window_param_init();
        break; // 弹窗初始化
    case S_LAYER_IN:
        layer_init_in();
        break; // 层级初始化
    case S_LAYER_OUT:
        layer_init_out();
        break; // 层级初始化

    case S_NONE:
        u8g2_ClearBuffer(&u8g2);
        switch (ui.index) // 直接选择页面
        {
        case M_WINDOW:
            window_proc();
            break;
        case M_SLEEP:
            sleep_proc();
            break;
        case M_MAIN:
            main_proc();
            break;
        case M_EDITOR:
            editor_proc();
            break;
        case M_KNOB:
            knob_proc();
            break;
        case M_KRF:
            krf_proc();
            break;
        case M_KPF:
            kpf_proc();
            break;
        case M_VOLT:
            volt_proc();
            break;
        case M_SETTING:
            setting_proc();
            break;
        case M_ABOUT:
            about_proc();
            break;
        }
    }
}

// OLED初始化函数
void Menu_Init()
{
    //	eeprom_init();
    //	hid_init();
    OLED_RST_Set();
    u8g2Init(&u8g2);

    ui_param_init();
    ui_init();

    ui.index = M_SLEEP;
    ui.state = S_NONE;
    ui.sleep = true;
    ui.fade = 1;

    win.l = (DISP_W - WIN_W) / 2;
    win.u = (DISP_H - WIN_H) / 2;

    tile.title_y_calc = TILE_INDI_S + (TILE_INDI_H - TILE_B_TITLE_H) / 2 + TILE_B_TITLE_H * 2;
    tile.title_y_trg_calc = TILE_INDI_S + (TILE_INDI_H - TILE_B_TITLE_H) / 2 + TILE_B_TITLE_H;

    list.line_n = DISP_H / LIST_LINE_H;

    u8g2_SetContrast(&u8g2, ui.param[DISP_BRI]);
    buf_ptr = u8g2_GetBufferPtr(&u8g2);
    buf_len = 8 * u8g2_GetBufferTileHeight(&u8g2) * u8g2_GetBufferTileWidth(&u8g2);
}
